namespace Nintest.Graph
{
    using System.Collections.Generic;
    using System.Linq;

    using Nintest.Collections;

    public class IncidenceGraph<TEdge, TVertex>
    {
        private readonly IMutableLookup<Vertex<TVertex>, Edge<TEdge, TVertex>, IMutableGrouping<Vertex<TVertex>, Edge<TEdge, TVertex>>> _outEdges;

        public IncidenceGraph()
        {
            _outEdges = CreateLookup();
        }

        public IEnumerable<Vertex<TVertex>> Vertices
        {
            get { return _outEdges.Select(g => g.Key); }
        }

        public IEnumerable<Edge<TEdge, TVertex>> GetOutEdges(Vertex<TVertex> source)
        {
            IMutableGrouping<Vertex<TVertex>, Edge<TEdge, TVertex>> grouping;
            var found = _outEdges.TryGetGrouping(source, out grouping);

            return found ? grouping : Enumerable.Empty<Edge<TEdge, TVertex>>();
        }

        public Vertex<TVertex> AddVertex(TVertex property)
        {
            var vertex = new Vertex<TVertex>(property);
            CreateOrGet(vertex);
            return vertex;
        }

        public Edge<TEdge, TVertex> AddEdge(Vertex<TVertex> source, Vertex<TVertex> target, TEdge property)
        {
            var edge = new Edge<TEdge, TVertex>(target, property);
            CreateOrGet(target);
            CreateOrGet(source).Add(edge);
            return edge;
        }

        private static IMutableLookup<Vertex<TVertex>, Edge<TEdge, TVertex>, IMutableGrouping<Vertex<TVertex>, Edge<TEdge, TVertex>>> CreateLookup()
        {
            return new Lookup<Vertex<TVertex>, Edge<TEdge, TVertex>, IMutableGrouping<Vertex<TVertex>, Edge<TEdge, TVertex>>>();
        }

        private static IMutableGrouping<Vertex<TVertex>, Edge<TEdge, TVertex>> CreateGrouping(Vertex<TVertex> source)
        {
            return new Grouping<Vertex<TVertex>, Edge<TEdge, TVertex>>(source);
        }

        private IMutableGrouping<Vertex<TVertex>, Edge<TEdge, TVertex>> CreateOrGet(Vertex<TVertex> vertex)
        {
            IMutableGrouping<Vertex<TVertex>, Edge<TEdge, TVertex>> grouping;
            if (!_outEdges.TryGetGrouping(vertex, out grouping))
            {
                grouping = CreateGrouping(vertex);
                _outEdges.Add(grouping);
            }

            return grouping;
        }
    }
}